/*
 *  Copyright (C) 2004 Cidero, Inc.
 *
 *  Permission is hereby granted to any person obtaining a copy of 
 *  this software to use, copy, modify, merge, publish, and distribute
 *  the software for any non-commercial purpose, subject to the
 *  following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY IN CONNECTION WITH THE SOFTWARE.
 * 
 *  File: $RCSfile: RadioServer.java,v $
 *
 */
package com.cidero.server;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Logger;

import org.cybergarage.upnp.Device;
import org.cybergarage.upnp.Service;
import org.cybergarage.upnp.device.InvalidDescriptionException;
import org.cybergarage.upnp.device.NotifyListener;
import org.cybergarage.upnp.device.SearchResponseListener;
import org.cybergarage.upnp.event.EventListener;
import org.cybergarage.upnp.ssdp.SSDPPacket;

import com.cidero.util.MrUtil;
import com.cidero.util.AppPreferences;
import com.cidero.util.NetUtil;

/**
 * Simple internet radio server.  Radio station data is stored in DIDL-Lite
 * files, and the data is output to UPnP devices upon request. 
 *
 * The server supports a proxy mode to support the synchronization of 
 * multiple devices.  When in proxy mode, the addresses of all the 
 * radio stations are modified to point to a local 'synchronous HTTP
 * server', which is used to relay data from a single internet
 * source to multiple devices that are requesting the same station.
 * Synchronization implementation is still a work in progress...
 */
public class RadioServer extends Device
{
  private final static Logger logger = Logger.getLogger("com.cidero.server");

  private final static String DESCRIPTION_FILE_NAME = 
       "com/cidero/server/description/RadioServer.xml";

  RadioServerConnectionManager connectionManager;
  RadioServerContentDirectory  contentDirectory;

  static AppPreferences   pref;

  String           dbDir;
  boolean          appendHostToFriendlyName = false;

  // Proxy server fields
  boolean          proxyModeEnabled = false;
  String           ipAddr;
  int              proxyServerPort;
  int              syncWaitMillisec;
  HTTPProxyServer  httpProxyServer = null;

  // Misc settings 
  boolean   verbose = false;

  // Option to allow automatic generation of a static version of device's
  // web pages (useful for building web site docs)
  String    staticWebPageDir = null;  // not enabled

  // Option to allow testing of all stations in database and writing
  // test results to file
  String    testResultsFile = null;  // not enabled
  
  /**
   * Constructor
   * 
   * @throws InvalidDescriptionException if an error occurs reading the 
   *         device description XML file
   */
  public RadioServer( String[] args ) throws InvalidDescriptionException
  {
    //
    // Invoke constructor of UPNP device superclass. This parses the XML
    // device and service descriptions, and builds in-memory representations
    //
    super( MrUtil.getResourceAsFile(DESCRIPTION_FILE_NAME) );

    //
    // Load preferences, then process command-line arguments. Command-line
    // settings override preference file equivalents 
    //
    loadPreferences();
    processCmdLineArgs( args );

    //
    // Override UDN from description.xml to make it unique for friendlyName,
    // hostName combinations. Cybergarage API requires a call to setUUID,
    // followed by a call to updateUDN, to reset the UDN. 
    //
    setUUID( "Cidero-" + getFriendlyName() + 
             "-" + NetUtil.getLocalHostName() );
    updateUDN();  // This sets the UDN to 'uuid:<UUID>'

    // If enabled, append hostname to friendly name to allow for 
    // multiple instances to be easily distinguished on GUI displays. 
    if( appendHostToFriendlyName )
      setFriendlyName( getFriendlyName() +
                     " (" + NetUtil.getLocalHostName() +")" );

    setNMPRMode( true );
    setWirelessMode( true );   // send redundant copies of SSDP packets
    
    // Assume using the default IP (address of 1st interface found)
    ipAddr = NetUtil.getDefaultLocalIPAddress();
    logger.info("Using IP address: " + ipAddr );

    //
    // Construct instances of the two UPnP services provided by every
    // UPnP MediaServer
    //
    connectionManager = new RadioServerConnectionManager( this );
    contentDirectory = new RadioServerContentDirectory( this );

    // If requested, generate HTML pages for database (for future Web
    // interface to the server, as well as for the Web site)
    if( staticWebPageDir != null )
    {
      try
      {
        // Creates file 'stationdb.html' in USER_HOME/.cidero for now
        contentDirectory.generateStationDbHtml( staticWebPageDir );
      }
      catch( IOException e )
      {
        logger.warning("IOException generating Web interface HTML" + e );
      }
    }

    // If requested, generate HTML pages for database (for future Web
    // interface to the server, as well as for the Web site)
    if( testResultsFile != null )
    {
      try
      {
        contentDirectory.testStations( testResultsFile );
      }
      catch( IOException e )
      {
        logger.warning("IOException testing radio stations" + e );
      }
    }

    if( proxyModeEnabled )
    {
      try
      {
        httpProxyServer = new HTTPProxyServer( proxyServerPort,
                                               syncWaitMillisec );
      }
      catch( IOException e )
      {
        logger.severe("Couldn't create HTTPProxyServer");
        System.exit(-1);
      }
    }

    logger.fine("Leaving RadioServer construtor ");
  }

  public void close()
  {
    if( httpProxyServer != null )
      httpProxyServer.close();
  }
  
  
  public void loadPreferences()
  {
    pref = new AppPreferences(".cidero");
    pref.load("RadioServer", "RadioServer" );   // appName, className

    // Check for override of default friendly name
    String friendlyName = pref.get("friendlyName");
    if( friendlyName != null )
      setFriendlyName( friendlyName );

    appendHostToFriendlyName = pref.getBoolean("appendHostToFriendlyName", 
                                               false );

    dbDir = pref.get( "databaseDir", "default" );

    if( dbDir.equalsIgnoreCase("default") )
    {
     dbDir = MrUtil.getResourcePath("db/radiodb");
     if( dbDir == null )
     {
       logger.severe("Can't find radio station db (fatal)");
       System.exit(-1);
     }
    }

    // Default server port is 18080
    proxyServerPort = pref.getInt( "proxyServerPort", 18080 );

    // Default syncWaitMillisec is 4 sec
    syncWaitMillisec = pref.getInt( "syncWaitMillisec", 4000 );

  }

  public String getDatabaseDir()
  {
    return dbDir;
  }
  
  public static AppPreferences getPreferences()
  {
    return pref;
  }

  public void setProxyServerPort(int port)
  {
    this.proxyServerPort = port;
  }
  public int getProxyServerPort()
  {
    return proxyServerPort;
  }

  public boolean isProxyModeEnabled()
  {
    return proxyModeEnabled;
  }

  /**
   * Generate dynamic HTML content for the given URI. If the uri is
   * not recognized, return null
   *
   * This routine is meant to be overridden by device applications that
   * provide support for dynamically generated presentation pages.
   *
   * There's probably a more 'standard' way of doing this - revisit (TODO)
   */
  public byte[] generateDynamicHttpContent( String uri )
  {
    logger.info("Generating dynamic content for URI: " + uri );

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

    if( uri.equalsIgnoreCase("/presentation/index.html") )
    {
      generateIndexHtml( outputStream );
    }
    else if( uri.equalsIgnoreCase("/presentation/stationdb.html") )
    {
      contentDirectory.generateStationDbHtml( outputStream );
    }
    else
    {
      return null;
    }

    byte[] content = outputStream.toByteArray();

    System.out.println("returning content, nbytes = " + content.length );

    return content;
  }

  /**
   * Generate the main page for the device (index.html)
   */
  public void generateIndexHtml( OutputStream outputStream )
  {
    PrintWriter writer = new PrintWriter( outputStream );

    RadioServerWebUtil.writeHtmlHeader( writer, "Cidero Radio Server Status" );

    // Generate a set of tables, one for each subfolder of the high-level
    // directory

    System.out.println("generateIndexHtml: Entered");

    writer.println("<br>");
    writer.println("<h2 align=\"center\">Radio Server Status</h2>" );
    writer.println("<br>");

    writer.println("<div id=\"pageContent\" title=\"content\">");

    writer.println("<p>");
    if( isProxyModeEnabled() )
      writer.println("Proxy Mode: Enabled");
    else
      writer.println("Proxy Mode: Disabled");
    writer.println("<br>");

    writer.println("Proxy Server Port: " + getProxyServerPort() );
    writer.println("<br>");

    if( isProxyModeEnabled() )
      writer.println("Number of Active Proxy Sessions: " +
                     httpProxyServer.getNumActiveSessions() );
    else
      writer.println("Number of Active Proxy Sessions: 0" );

    writer.println("<br>");

    writer.println("<br>");

    writer.println("<a href=\"stationdb.html\">Station Database</a>");

    writer.println("</p>");

    writer.println("</div>");

    writer.println("</body>");
    writer.println("</html>");
    writer.flush();

    System.out.println("generateIndexHtml: Leaving");

  }


  /**
   * @return  Returns the IP Address of the server, as a string like
   *          '192.168.1.10'
   *
   *  TODO: Strategy needs to be improved for hosts with multiple LAN 
   *  interfaces
   */
  public String getIPAddress()
  {
    return NetUtil.getDefaultLocalIPAddress();
  }
  
  public void usage()
  {
    logger.info("Usage: RadioServer [-v] [-p] [-s syncWaitPeriod] [-w]\n");
    logger.info(" [-v]         Verbose mode\n");
    logger.info(" [-p]         Enable proxy mode (synchronous server)");
    logger.info(" [-s period]  Sync wait period, in milliseconds. This");
    logger.info("              is the amount of time to wait for additional");
    logger.info("              devices to join a 'synchronous playback session");
    logger.info("              following the start of a session by a single ");
    logger.info("              device. Default is 4000 (4 seconds) ");
    logger.info(" [-w dir]     Enable generation of static version of web");
    logger.info("              pages at startup, using the specified directory.");
    logger.info("              (Useful for generating Web site's 'factory settings' web interface samples");
    logger.info(" [-t file]    Test all radio stations in database, printing");
    logger.info("              results to specified file");

    System.exit(-1);
  }
  
  public void processCmdLineArgs( String[] args )
  {
    int optind;

    for( optind = 0; optind < args.length; optind++ )
    {
      if( args[optind].equals("-v") )
      {
        verbose = true;
      }
      else if( args[optind].equals("-p") )
      {
        proxyModeEnabled = true;
        optind++;
      }
      else if( args[optind].equals("-s") )
      {
        optind++;
        syncWaitMillisec = Integer.parseInt( args[optind] );
        optind++;
      }
      else if( args[optind].equals("-w") )
      {
        optind++;
        staticWebPageDir = args[optind++];
      }
      else if( args[optind].equals("-t") )
      {
        optind++;
        testResultsFile = args[optind++];
      }
      else
      {
        usage();
        break;
      }
    }
  }

  /**
   * Main routine
   * 
   */
  public static void main( String args[] )
  {
    try
    {
      final RadioServer radioServer = new RadioServer( args );

      if( radioServer.isProxyModeEnabled() )
      {
        logger.info("Starting RadioServer, with HTTP proxy server port: " +
                    radioServer.getProxyServerPort() );
      }
      else
      {
        logger.info("Starting RadioServer with HTTP proxy mode disabled" );
      }
      
      radioServer.start();

      //
      // Make sure device stop routine is invoked at shutdown. It's
      // beneficial to send out bye-bye messages to all control points
      // if possible
      //
      Runtime.getRuntime().addShutdownHook( new Thread() {
          public void run() 
          {
            System.out.println("Java runtime shutting down");
            radioServer.stop();  // UPnP device stop
            radioServer.close();
          }
      });

    }
    catch (InvalidDescriptionException e)
    {
      logger.severe( "Error starting RadioServer" + e );
      System.exit(-1);
    }

  }

}
